package com.googlecode.xbeandatasource.jasperreport;

import java.util.Collection;

import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRField;
import net.sf.jasperreports.engine.JRRewindableDataSource;

import org.apache.commons.jxpath.JXPathContext;
import org.apache.commons.jxpath.JXPathNotFoundException;
import org.apache.commons.jxpath.ri.JXPathContextFactoryReferenceImpl;

/**
 * Enable to use XPath to parse and access a POJO.
 * 
 * @author Johny Huang
 * @since 0.1
 * 
 */
public class XBeanDataSource implements JRRewindableDataSource {

	private Object rootObj;
	
	private Object curObj;

	private int count = 0;

	private int objSize = 1;

	private Object[] values;
	
	private JXPathContext context;

	/**
	 * @param obj as the root object
	 * @throws JRException
	 */
	public XBeanDataSource(Object obj) throws JRException {
		if (obj == null) {
			throw new JRException("The parameter obj can not be null!");
		}
		this.rootObj=obj;
		this.curObj=obj;
		convertCurrentObjToValues();
	}
	
	/**
	 * @param obj as the root object
	 * @param expression
	 * @throws JRException
	 */
	public XBeanDataSource(Object obj,String expression) throws JRException {
		if (obj == null) {
			throw new JRException("The parameter obj can not be null!");
		}
		try{
			this.rootObj=obj;
			JXPathContext ctx=JXPathContextFactoryReferenceImpl.newInstance().newContext(null,obj);
			this.curObj=ctx.getValue(expression);
		}catch(JXPathNotFoundException ex){
			throw new JRException("Can not find obj by this expression.",ex);
		}
		convertCurrentObjToValues();
	}

	/* (non-Javadoc)
	 * @see net.sf.jasperreports.engine.JRDataSource#getFieldValue(net.sf.jasperreports.engine.JRField)
	 */
	public Object getFieldValue(JRField jrField) throws JRException {
		if (jrField == null) {
			throw new JRException("JRField is null!");
		}
		Object obj = null;

		try {
			obj = context.getValue(jrField.getDescription());
		} catch (JXPathNotFoundException ex) {
			obj = null;
		}
		return obj;
	}

	/* (non-Javadoc)
	 * @see net.sf.jasperreports.engine.JRDataSource#next()
	 */
	public boolean next() throws JRException {
		if(objSize > count){
			context=JXPathContextFactoryReferenceImpl.newInstance().newContext(null,values[count]);
		}
		return objSize > count++;
	}
	
	/* (non-Javadoc)
	 * @see net.sf.jasperreports.engine.JRRewindableDataSource#moveFirst()
	 */
	public void moveFirst() throws JRException {
		this.curObj=this.rootObj;
		convertCurrentObjToValues();
	}
	
	/**
	 * @param expression
	 * @return
	 * @throws JRException
	 */
	public XBeanDataSource subDataSource(String expression) throws JRException{
		return new XBeanDataSource(curObj,expression);
	}
	
	/**
	 * @return
	 * @throws JRException
	 */
	public XBeanDataSource subDataSource() throws JRException{
		return subDataSource(".");
	}
	
	/**
	 * @param expression base on the root object
	 * @return A new data source
	 * @throws JRException
	 */
	public XBeanDataSource dataSource(String expression)throws JRException{
		return new XBeanDataSource(rootObj,expression);
	}
	
	/**
	 * @return a new data source of the root object
	 * @throws JRException
	 */
	public XBeanDataSource dataSource()throws JRException{
		return dataSource(".");
	}
	
	private void convertCurrentObjToValues(){
		if (curObj instanceof Collection) {
			values = ((Collection) curObj).toArray();
			objSize = values.length;
		} else if (curObj.getClass().isArray()) {
			values = (Object[]) curObj;
			objSize = values.length;
		}else{
			objSize=1;
			values=new Object[]{curObj};
		}
	}

}
